#import "UniqueKey.h"
#import "Projects.h"


static BOOL _Debug = YES;

@implementation Projects

/******************************************************************************
* Create two instances of the UniqueKey object to grab blocks of <count> keys for
* our use during inserts.  The count is set low so you can see the SQL reserve
* blocks of keys.  Up the count to something larger for it to be of use.
*
* The login information (connection dictionary) in the model has been blanked out,
* since you may elect to have the tables in some database other than PEOPLE, so we
* elect to run a login panel.  Once the login is complete we can get the
* connection dictionary from the adaptor and use that information to allow
* UniqueKey to login and create its separate connection for key reservation.
*
* Note the content views of the employee and project boxes and paste in the
* default content.
******************************************************************************/
- appDidInit:sender
{
    EOAdaptor *eoAdaptor;

    dbChannel = [[(id)[employeeController dataSource] databaseChannel] retain];
    dbContext = [[dbChannel databaseContext] retain];
    
    if(_Debug) [[dbChannel adaptorChannel] setDelegate:self];
    
    projectEntity    = [[(id)[projectController dataSource] entity] retain];
    employeeEntity   = [[(id)[employeeController dataSource] entity] retain];
    empProjectEntity = [[(id)[projectEntity relationshipNamed:@"toEmpProjects"] destinationEntity] retain];
   	empProjectDDS	= [[EODatabaseDataSource alloc] initWithDatabaseChannel: dbChannel
						entityNamed: [empProjectEntity name]]; 
    eoAdaptor = [[[dbChannel adaptorChannel] adaptorContext] adaptor];
    if(![eoAdaptor runLoginPanelAndValidateConnectionDictionary]) [NXApp terminate:self];
    [UniqueKey setConnectionDictionary:[eoAdaptor connectionDictionary]];
    
    projectUniqueKey  = [[[UniqueKey alloc] initWithEntity:projectEntity  count:5] retain];
    employeeUniqueKey = [[[UniqueKey alloc] initWithEntity:employeeEntity count:5] retain];
    if(!projectUniqueKey || !employeeUniqueKey) [NXApp terminate:self];

    [self setFetchOrderFor:projectController                with:@"ProjectName" order:EOAscendingOrder];
    [self setFetchOrderFor:addProjectsForEmployeeController with:@"ProjectName" order:EOAscendingOrder];
    [self setFetchOrderFor:employeeController               with:@"LastName"    order:EOAscendingOrder];
    [self setFetchOrderFor:addEmployeesOnProjectController  with:@"LastName"    order:EOAscendingOrder];
    
    [addProjectsForEmployeeController setSelectsFirstObjectAfterFetch:NO];
    [addEmployeesOnProjectController  setSelectsFirstObjectAfterFetch:NO];

    inspectorWindow=[swapBox window];
    [self projectInspector:nil];		// swap in the project content view
    [[inspectorWindow center] makeKeyAndOrderFront:nil];
    return self;
}


/******************************************************************************
* Set a controller's data source to fetch sorted by a given attribute name
* and order.
******************************************************************************/
- setFetchOrderFor:(EOController*)controller with:(NSString*)attributeName order:(EOOrdering)order
{
    id		dataSource = [controller dataSource];
    id		attribute  = [[dataSource entity] attributeNamed:attributeName];
    NSArray	*orderArray;
    
    orderArray = [NSArray arrayWithObject:
    	[EOAttributeOrdering attributeOrderingWithAttribute:attribute ordering:order]];
    [dataSource setFetchOrder:orderArray];
    return self;
}


/******************************************************************************
* Accept the users selection for either the Employee or Project inspector.  Swap
* in the appropriate content view.  
******************************************************************************/
- employeeInspector:sender
{
    [inspectorWindow disableFlushWindow];
    [swapBox setContentView:[employeeBox contentView]];
    [employeeController fetch];
    [swapBox display];
    [[inspectorWindow reenableFlushWindow] flushWindowIfNeeded];
    return self;
}

- projectInspector:sender
{
    [inspectorWindow disableFlushWindow];
    [swapBox setContentView:[projectBox contentView]];
    [projectController fetch];
    [swapBox display];
    [[inspectorWindow reenableFlushWindow] flushWindowIfNeeded];
    return self;
}


/******************************************************************************
* Utility to create an intermediate table empProject EOGenericRecord.
******************************************************************************/
- createEmpProject:(NSNumber*)empId:(NSNumber*)projectId
{
    EOGenericRecord *empProject = [empProjectDDS createObject];
    
    [empProject setObject:projectId forKey:@"ProjectId"];
    [empProject setObject:empId forKey:@"EmpId"];
    return empProject;
}


/******************************************************************************
* Bring up the add employee selection panel and start a modal session.  Construct 
* and 'otherEmployees' qualifier to select employees that do not already appear
* in the EmployeesOnProject detail view.
******************************************************************************/
- addEmployeesOnProject:sender
{
    EOGenericRecord	*project   = [(id)[employeesOnProjectController dataSource] masterObject];
    NSNumber		*projectId = [project objectForKey:@"ProjectId"];
    EOQualifier		*otherEmployees;
    NSString		*qualifierString;
    
    qualifierString = [NSString stringWithFormat:
    	@"EmpId not in (SELECT z1.EMP_ID FROM EMPLOYEE z1, EMP_PROJECT z2 "
	@"WHERE z1.EMP_ID = z2.EMP_ID AND z2.PROJECT_ID = %@)",projectId];
	
    otherEmployees = [[[EOQualifier alloc]
     	initWithEntity:employeeEntity qualifierFormat:qualifierString] autorelease];
	
    [(id)[addEmployeesOnProjectController dataSource] setQualifier:otherEmployees];
    [addEmployeesOnProjectController clearSelection];
    [addEmployeesOnProjectController fetch];
    [[addEmployeesPanel center] makeKeyAndOrderFront:nil];
    [NXApp runModalFor:addEmployeesPanel];
    [addEmployeesPanel orderOut:self];
    [projectController fetch];
    return self;
}

- addEmployeesOnProjectOK:sender
{
	NSArray			*eArray;
	NSEnumerator 	*eEnumerator;
	EOGenericRecord	*project;
	NSNumber		*projectId;
	EOGenericRecord	*employee;
    EOGenericRecord	*empProject;

    eArray		= [addEmployeesOnProjectController selectedObjects];
    eEnumerator	= [eArray objectEnumerator];
    project	= [(id)[employeesOnProjectController dataSource] masterObject];
    projectId	= [project objectForKey:@"ProjectId"];
    [dbContext beginTransaction];
    while((employee = [eEnumerator nextObject]) != nil) {
	empProject = [self createEmpProject:[employee objectForKey:@"EmpId"]:projectId];	
	[dbChannel insertObject:empProject];
    }
    [dbContext commitTransaction];
    [NXApp stopModal];
    return self;
}


/******************************************************************************
* Bring up the add project selection panel and start a modal session.  Construct 
* and 'otherProjects' qualifier to select project that do not already appear
* in the ProjectsForEmployee detail view.  We use an imbedded select here with
* '.' chars in the qualifier string.  These names will not be converted, since they
* contain '.' chars, so we have the ability to write qualifiers that
* reference external names it we like.
******************************************************************************/
- addProjectsForEmployee:sender
{
    EOGenericRecord	*employee  = [(id)[projectsForEmployeeController dataSource] masterObject];
    NSNumber		*empId     = [employee objectForKey:@"EmpId"];
    EOQualifier		*otherProjects;
    NSString		*qualifierString;
    
    qualifierString = [NSString stringWithFormat:
    	@"ProjectId not in (SELECT z1.PROJECT_ID FROM PROJECT z1, EMP_PROJECT z2 "
	@"WHERE z1.PROJECT_ID = z2.PROJECT_ID AND z2.EMP_ID = %@)",empId];

    otherProjects = [[[EOQualifier alloc]
    	initWithEntity:projectEntity qualifierFormat:qualifierString] autorelease];

    [(id)[addProjectsForEmployeeController dataSource] setQualifier:otherProjects];
    [addProjectsForEmployeeController clearSelection];
    [addProjectsForEmployeeController fetch];
    [[addProjectsPanel center] makeKeyAndOrderFront:nil];
    [NXApp runModalFor:addProjectsPanel];
    [addProjectsPanel orderOut:self];
    [employeeController fetch];
    return self;
}

- addProjectsForEmployeeOK:sender
{
    NSArray			*pArray;
    NSEnumerator	*pEnumerator;
    EOGenericRecord	*employee;
    NSNumber		*empId;
    EOGenericRecord	*project;
    EOGenericRecord	*empProject;

	pArray		= [addProjectsForEmployeeController selectedObjects];
    pEnumerator	= [pArray objectEnumerator];
    employee	= [(id)[projectsForEmployeeController dataSource] masterObject];
    empId		= [employee objectForKey:@"EmpId"];

    [dbContext beginTransaction];
    while((project = [pEnumerator nextObject]) != nil) {
	empProject = [self createEmpProject:empId:[project objectForKey:@"ProjectId"]];
	[dbChannel insertObject:empProject];
    }
    [dbContext commitTransaction];
    [NXApp stopModal];
    return self;
}


/******************************************************************************
* Delete the intermediate table entries for the selected employee.  To do this we
* get the selected master object and ask it for the enumerator of empProject
* objects.  Since each empProject object can be asked objectForKey:@"Employee", we
* see if that empProject object has a corresponding employee in the selected employee
* array (eArray).  We choose to use the method indexOfObjectIdenticalTo: to do the
* check since it uses pointer equality.  This works because object uniquing is
* enabled by default.
******************************************************************************/
- removeSelectedEmployeesOnProject:sender
{
    NSArray		*eArray;
    EOGenericRecord	*project;
    NSEnumerator	*epEnumerator;
    EOGenericRecord	*empProject;

	eArray			= [employeesOnProjectController selectedObjects];
    project			= [(id)[employeesOnProjectController dataSource] masterObject];
    epEnumerator	= [[project objectForKey:@"toEmpProjects"] objectEnumerator];

    [dbContext beginTransaction];
    while((empProject=[epEnumerator nextObject]) != nil)
	if([eArray indexOfObjectIdenticalTo:[empProject objectForKey:@"toEmployee"]]!=NSNotFound)
		[dbChannel deleteObject:empProject];
    [dbContext commitTransaction];
    [projectController fetch];
    return self;
}


/******************************************************************************
* Delete the intermediate table entries for the selected project.  To do this we
* get the selected master object and ask it for the enumerator of empProject
* objects.  Since each empProject object can be asked objectForKey:@"Project", we
* see if that empProject object has a corresponding project in the selected project
* array (pArray).  We choose to use the method indexOfObjectIdenticalTo: to do the
* check since it uses pointer equality.  This works because object uniquing is
* enabled by default.
******************************************************************************/
- removeSelectedProjectsForEmployee:sender
{
    NSArray		*pArray;
    EOGenericRecord	*employee;
    NSEnumerator	*epEnumerator;
    EOGenericRecord	*empProject;
    
	pArray			= [projectsForEmployeeController selectedObjects];
    employee		= [(id)[projectsForEmployeeController dataSource] masterObject];
    epEnumerator	= [[employee objectForKey:@"toEmpProjects"] objectEnumerator];

    [dbContext beginTransaction];
    while((empProject=[epEnumerator nextObject]) != nil)
	if([pArray indexOfObjectIdenticalTo:[empProject objectForKey:@"toProject"]]!=NSNotFound)
		[dbChannel deleteObject:empProject];
    [dbContext commitTransaction];
    [employeeController fetch];
    return self;
}


/******************************************************************************
* Generate unique keys for the new object.  Use the instance of UniqueKey
* to dole out a key from its internal buffer.  The UniqueKey object has its
* own channel to the DB which it uses to allocate blocks of keys.
******************************************************************************/
- (BOOL)controller:(EOController *)controller willInsertObject:object atIndex: (unsigned)newIndex
{
    if(controller==employeeController) {
	NSArray *emptyArray1 = [[[NSArray alloc] init] autorelease];
	NSArray *emptyArray2 = [[[NSArray alloc] init] autorelease];
	NSNumber *empId	     = [NSNumber numberWithInt:[employeeUniqueKey nextKey]];
	
        [object setObject:empId forKey:@"EmpId"];
	[object setObject:@"<FirstName>" forKey:@"FirstName"];
	[object setObject:@"<LastName>" forKey:@"LastName"];
	[object setObject:@"<Phone>" forKey:@"Phone"];
  	[object setObject:emptyArray1 forKey:@"toEmpProjects"];
  	[object setObject:emptyArray2 forKey:@"toProjects"];
	return YES;
    }
    if(controller==projectController) {
	NSArray *emptyArray1 = [[[NSArray alloc] init] autorelease];
	NSArray *emptyArray2 = [[[NSArray alloc] init] autorelease];
	NSNumber *projectId  = [NSNumber numberWithInt:[projectUniqueKey nextKey]];
	
        [object setObject:projectId forKey:@"ProjectId"];
	[object setObject:@"<ProjectName>" forKey:@"ProjectName"];
  	[object setObject:emptyArray1 forKey:@"toEmpProjects"];
  	[object setObject:emptyArray2 forKey:@"toEmployees"];
	return YES;
    }
    return NO;
}


/******************************************************************************
* When a project or employee is deleted, make sure we delete the entry in the
* empProject intermediate table.  Both the project and employee entities have
* a toEmpProjects relationship which we can use to delete the intermediate
* table records.
******************************************************************************/
- (BOOL)controller:controller willDeleteObject:object
{
    NSArray	 	*epArray;
    NSEnumerator 	*epEnumerator;
    EOGenericRecord	*empProject;
	
    // The employee and project entities have a toEmpProjects
    // relationship to the intermediate table
    if(controller==projectController ||
       controller==employeeController) {
  	[dbContext beginTransaction];
	epArray=[object objectForKey:@"toEmpProjects"];
	epEnumerator=[epArray objectEnumerator];
    	while((empProject = [epEnumerator nextObject]) != nil)
		[dbChannel deleteObject:empProject];
  	[dbContext commitTransaction];
    }
    return YES;
}


/******************************************************************************
* Echo SQL when debug is enabled.
******************************************************************************/
- (EODelegateResponse)adaptorChannel:channel willEvaluateExpression:(NSMutableString *)expression
{
    NSLog(expression);
    return EODelegateApproves;
}

- (void)dealloc
{
	[dbContext  release];
	[dbChannel  release];
	[projectEntity  release];
	[employeeEntity  release];
	[empProjectEntity  release];
	[projectUniqueKey release];
	[employeeUniqueKey  release];
	[super dealloc];
   
}

@end
